structure MipsGen : CODEGEN =
struct
    structure Frame = MipsFrame

    structure A = Assem
    structure E = ErrorMsg
    structure F = MipsFrame
    structure M = Temp
    structure R = Tree
    structure S = Symbol
    structure T = Temp
		      
    fun codegen frame stm =
	let val ilist = ref []
			
	    val int = Int.toString
		      
	    fun emit x = ilist := x :: !ilist
			 
	    fun result gen = let val t = T.newtemp()
			     in ( gen t
				; t
				)
			     end
			     
	    val calldefs = F.calldefs
			   
	    fun munchStm (R.SEQ (s, s')) = ( munchStm s
					   ; munchStm s' )
					   
	      | munchStm (R.LABEL l) = emit (A.LABEL { assem= (S.name l) ^ ":\n",
						       lab=l })
				       
	      | munchStm (R.JUMP (e, ls)) = emit (A.OPER { assem="jr `s0\n",
							   src=[munchExp e],
							   dst=[],
							   jump=SOME ls })
				    
	      | munchStm (R.CJUMP (R.EQ, R.CONST i, R.CONST i', l, l'))  = if i=i' then 
	      		 	  	 	    	    	   	    emit (A.OPER { assem="b " ^ (S.name l) ^ "\n",src=[], dst=[] ,jump=SOME [l] })  
									   else ()	
			    
	      | munchStm (R.CJUMP (R.EQ, e, e', l, l'))  = emit (A.OPER { assem="beq `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })

               | munchStm (R.CJUMP (R.NE, R.CONST i, R.CONST i', l, l'))  = if i<>i' then 
	      		 	  	 	    	    	   	    emit (A.OPER { assem="b " ^ (S.name l) ^ "\n",src=[], dst=[] ,jump=SOME [l] })  
									   else
									       ()
									     
							   
	      | munchStm (R.CJUMP (R.NE, e, e', l, l')) = emit (A.OPER { assem="bne `s0, `s1, " ^ (S.name l) ^ "\n",
									 src=[munchExp e, munchExp e'],
									 dst=[],
									 jump=SOME [l, l'] })

              | munchStm (R.CJUMP (R.LT, R.CONST i, R.CONST i', l, l'))  = if i<i' then 
	      		 	  	 	    	    	   	    emit (A.OPER { assem="b " ^ (S.name l) ^ "\n",src=[], dst=[] ,jump=SOME [l] })  
									   else ()
							  
	      | munchStm (R.CJUMP (R.LT, e, e', l, l'))  = emit (A.OPER { assem="blt `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })

	       | munchStm (R.CJUMP (R.GT, R.CONST i, R.CONST i', l, l'))  = if i>i' then 
	      		 	  	 	    	    	   	    emit (A.OPER { assem="b " ^ (S.name l) ^ "\n",src=[], dst=[] ,jump=SOME [l] })  
									   else ()							  
							   
	      | munchStm (R.CJUMP (R.GT, e, e', l, l'))  = emit (A.OPER { assem="bgt `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })

              | munchStm (R.CJUMP (R.LE, R.CONST i, R.CONST i', l, l'))  = if i<=i' then 
	      		 	  	 	    	    	   	    emit (A.OPER { assem="b " ^ (S.name l) ^ "\n",src=[], dst=[] ,jump=SOME [l] })  
									   else ()									  
							   
	      | munchStm (R.CJUMP (R.LE, e, e', l, l'))  = emit (A.OPER { assem="ble `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })

              | munchStm (R.CJUMP (R.GE, R.CONST i, R.CONST i', l, l'))  = if i>=i' then 
	      		 	  	 	    	    	   	    emit (A.OPER { assem="b " ^ (S.name l) ^ "\n",src=[], dst=[] ,jump=SOME [l] })  
									   else ()
							   
	      | munchStm (R.CJUMP (R.GE, e, e', l, l'))  = emit (A.OPER { assem="bge `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.ULT, e, e', l, l')) = emit (A.OPER { assem="bltu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.UGT, e, e', l, l')) = emit (A.OPER { assem="bgtu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.ULE, e, e', l, l')) = emit (A.OPER { assem="bleu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.CJUMP (R.UGE, e, e', l, l')) = emit (A.OPER { assem="bgeu `s0, `s1, " ^ (S.name l) ^ "\n",
									  src=[munchExp e, munchExp e'],
									  dst=[],
									  jump=SOME [l, l'] })
							   
	      | munchStm (R.MOVE (R.MEM (R.CONST i) , e')) = emit (A.OPER { assem="sw `s1, "^ (int i) ^ "(`s0) \n",
								 src=[munchExp e'],
								 dst=[],
								 jump=NONE })
	      |munchStm (R.MOVE (R.MEM e, e')) = emit (A.OPER { assem="sw `s1, (`s0)\n",
								 src=[munchExp e, munchExp e'],
								 dst=[],
								 jump=NONE })
						  
	      | munchStm (R.MOVE (R.TEMP t, e)) = emit (A.OPER { assem="lw `d0, (`s0)\n",
								 src=[munchExp e],
								 dst=[t],
								 jump=NONE })
						  
	      | munchStm (R.EXP e) = ignore (munchExp e)
				     
	      | munchStm _ = E.impossible "CODEGEN STM MISMATCH"
			     
			     
	    and munchExp (R.MEM (R.BINOP (R.PLUS, e, R.CONST i))) = result (fn r => emit (A.OPER { assem="lw `d0, " ^ (int i) ^ "(`s0)\n", (* addi limited to 16 bits *)
										    src=[munchExp e],
										    dst=[r],
										    jump=NONE }))

	      | munchExp (R.MEM (R.BINOP (R.PLUS, R.CONST i, e))) = result (fn r => emit (A.OPER { assem="lw `d0, " ^ (int i) ^ "(`s0)\n", (* addi limited to 16 bits *)
										    src=[munchExp e],
										    dst=[r],
										    jump=NONE }))							   
              (* MEM CONST ? *)



	      | munchExp (R.BINOP (R.PLUS, e, R.CONST i)) = result (fn r => emit (A.OPER { assem="addi `d0, `s0, " ^ (int i) ^ "\n", (* addi limited to 16 bits *)
										    src=[munchExp e],
										    dst=[r],
										    jump=NONE }))
	      
	      | munchExp (R.BINOP (R.PLUS, R.CONST i, e)) = result (fn r => emit (A.OPER { assem="addi `d0, `s0, " ^ (int i) ^ "\n", (* addi limited to 16 bits *)
								          src=[munchExp e],
										    dst=[r],
										    jump=NONE }))
			

              | munchExp (R.BINOP (R.PLUS, e, e')) = result (fn r => emit (A.OPER { assem="add `d0, `s0, `s1\n", (* addi limited to 16 bits *)
										    src=[munchExp e, munchExp e'],
										    dst=[r],
										    jump=NONE }))
						     
	      
	      | munchExp (R.BINOP (R.MINUS, e, e')) = result (fn r => emit (A.OPER { assem="sub `d0, `s0, `s1\n",
										     src=[munchExp e, munchExp e'],
										     dst=[r],
										     jump=NONE }))
	
              (* Optimization for MULTIPLICATIONS *)
		
	      | munchExp (R.BINOP (R.MUL, e, R.CONST i)) = result ( 
							       let val k = Math.ln (Real.fromInt i) / Math.ln(2.0)
							           val k1= trunc k
								       in 
							      if (k1 = ceil k) then
							      fn r => emit (A.OPER { assem="sll `d0, `s0, " ^ (int k1) ^ "\n", (* NB pseudo instruction *)
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE })
							      else fn r => emit (A.OPER { assem="mul `d0, `s0, `s1\n", (* NB pseudo instruction *)
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE })
							   end )
							  
	      
	      | munchExp (R.BINOP (R.MUL, e, e')) = result (fn r => emit (A.OPER { assem="mul `d0, `s0, `s1\n", (* NB pseudo instruction *)
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))
						    
	      | munchExp (R.BINOP (R.DIV, e, e')) = result (fn r => emit (A.OPER { assem="div `d0, `s0, `s1\n", (* NB pseudo instruction *)
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))

	      | munchExp (R.BINOP (R.AND, R.CONST i,e)) = result (fn r => emit (A.OPER { assem="andi `d0, `s0 "^ (int i) ^ "\n",
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE }))

	      | munchExp (R.BINOP (R.AND, e, R.CONST i)) = result (fn r => emit (A.OPER { assem="andi `d0, `s0 "^ (int i) ^ "\n",
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE }))
						    
	      | munchExp (R.BINOP (R.AND, e, e')) = result (fn r => emit (A.OPER { assem="and `d0, `s0, `s1\n",
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))
	      | munchExp (R.BINOP (R.OR, R.CONST i,e)) = result (fn r => emit (A.OPER { assem="ori `d0, `s0 "^ (int i) ^ "\n",
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE }))

	      | munchExp (R.BINOP (R.OR, e, R.CONST i)) = result (fn r => emit (A.OPER { assem="ori `d0, `s0 "^ (int i) ^ "\n",
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE }))					    
	      | munchExp (R.BINOP (R.OR, e, e')) = result (fn r => emit (A.OPER { assem="or `d0, `s0, `s1\n",
										  src=[munchExp e, munchExp e'],
										  dst=[r],
										  jump=NONE }))
						   
	      | munchExp (R.BINOP (R.LSHIFT, e, e')) = result (fn r => emit (A.OPER { assem="sll `d0, `s0, `s1\n",
										      src=[munchExp e, munchExp e'],
										      dst=[r],
										      jump=NONE }))
						       
	      | munchExp (R.BINOP (R.RSHIFT, e, e')) = result (fn r => emit (A.OPER { assem="srl `d0, `s0, `s1\n",
										      src=[munchExp e, munchExp e'],
										      dst=[r],
										      jump=NONE }))
						       
	      | munchExp (R.BINOP (R.ARSHIFT, e, e')) = result (fn r => emit (A.OPER { assem="sra `d0, `s0, `s1\n",
										       src=[munchExp e, munchExp e'],
										       dst=[r],
										       jump=NONE }))

	      | munchExp (R.BINOP (R.XOR, R.CONST i,e)) = result (fn r => emit (A.OPER { assem="xori `d0, `s0 "^ (int i) ^ "\n",
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE }))

	      | munchExp (R.BINOP (R.XOR, e, R.CONST i)) = result (fn r => emit (A.OPER { assem="xori `d0, `s0 "^ (int i) ^ "\n",
										   src=[munchExp e],
										   dst=[r],
										   jump=NONE }))
	      | munchExp (R.BINOP (R.XOR, e, e')) = result (fn r => emit (A.OPER { assem="xor `d0, `s0, `s1\n",
										   src=[munchExp e, munchExp e'],
										   dst=[r],
										   jump=NONE }))
						    
						    
	      | munchExp (R.MEM e) = result (fn r => emit (A.OPER { assem="lw `d0, `s0\n",
								    src=[munchExp e],
								    dst=[r],
								    jump=NONE }))
				     
	      | munchExp (R.TEMP t) = t
				      
	      | munchExp (R.ESEQ (s, e)) = ( munchStm s
					   ; munchExp e )
					   
	      | munchExp (R.NAME l) = result (fn r => emit (A.OPER { assem="la `d0, " ^ (S.name l) ^ "\n",
								     src=[],
								     dst=[r],
								     jump=NONE }))
				      
	      | munchExp (R.CONST i) = result (fn r => emit (A.OPER { assem="li `d0, " ^ (int i) ^ "\n",
								      src=[],
								      dst=[r],
								      jump=NONE }))
				       
	      | munchExp (R.CALL (f, es)) = result (fn r => ( emit (A.OPER { assem="jal `s0\n",
									     src=(munchExp f)::(munchArgs es),
									     dst=r::calldefs,
									     jump=NONE })
							    ; emit (A.MOVE { assem= "move `d0, `s0\n",
									     src=F.RV,
									     dst=r })
							    ))
					    
	    and munchArgs es = ListPair.map (fn (t, e) => let val src = munchExp e
							  in ( emit (A.MOVE { assem="move `d0, `s0\n",
									      src=src,
									      dst=t })
							     ; src)
							  end)
					    (F.args, es)
		               handle ListPair.UnequalLengths => E.impossible "CALL: MORE ARGS THAN REGISTERS"

	in ( munchStm stm
	   ; rev (!ilist)
	   )
	end
end
